/*
 * locks.c
 *
 *  Created on: Mar 9, 2011
 *      Author: s126135
 */

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>

#include "../mod_general.h"
#include "../terminals.h"
#include "../list.h"

typedef struct {
	char* name;
	int locked;
	int disabled;
} node_t;

typedef struct {
	int id;
	int job;
} request_t;

typedef struct {
	char* name;
	list* reqids;
} category_t;

list* lock_reqs;
list* nodes;
terminal_t* term;

int next_request = 1;

void unlock_job() {
	int i, j;
	node_t* node;
	category_t* cat;
	request_t* req;
	char buffer[64];

	for (i = list_size(lock_reqs) - 1; i >= 0; i--) {
		cat = (category_t*) list_get(lock_reqs, i);
		if (list_size(cat->reqids) == 0)
			continue;
		req = (request_t*) list_get(cat->reqids, 0);
		if (req == NULL) {
			fprintf(stderr, "BUG: locks: unable to get first request (size: %d)\n", list_size(cat->reqids));
			continue;
		}
		for (j = list_size(nodes) - 1; j >= 0; j--) {
			node = list_get(nodes, j);
			if (!node->locked && !node->disabled) {
				node->locked = req->job;
				sprintf(buffer, "unlocked %d %s", req->id, node->name);
				terminal_write_to_daemon(term, buffer);
				free(list_remove(cat->reqids, 0));
				return;
			}
		}
	}
}

void print_status(int output) {
	int i;
	node_t* node;
	category_t* cat;
	char buffer[64];

	for (i = list_size(nodes) - 1; i >= 0; i--) {
		node = list_get(nodes, i);
		if (node->locked)
			sprintf(buffer, " * node %s [%s] locked %d", node->name, (node->disabled ? "disabled" : "enabled"), node->locked);
		else if (node->disabled)
			sprintf(buffer, " * node %s disabled %d", node->name, node->disabled);
		else
			sprintf(buffer, " * node %s available", node->name);
		//terminal_write_to_daemon(term, buffer);
		write(output, buffer, strlen(buffer) + 1);
	}
	for (i = list_size(lock_reqs) - 1; i >= 0; i--) {
		cat = list_get(lock_reqs, i);
		sprintf(buffer, " * category %s %d requests", cat->name, list_size(cat->reqids));
		//terminal_write_to_daemon(term, buffer);
		write(output, buffer, strlen(buffer) + 1);
	}
}

void* casper_locks(void* arg) {
	char *cmd, *ptr;
	node_t* node;
	category_t* cat;
	request_t* req;
	int i, j, job_id;
	char buffer[64];

	while ((cmd = terminal_read_from_daemon(term)) != NULL) {
		if (!strcmp(cmd, "locks")) {
			print_status(term->pins.terminal_write);
		}
		else if (!strncmp(cmd, "locks ", 6)) {
			ptr = cmd + 6;
			if (!strncmp(ptr, "unlock ", 7)) {
				ptr += 7;
				for (i = list_size(nodes) - 1; i >= 0; i--) {
					node = (node_t*) list_get(nodes, i);
					if (!strcmp(node->name, ptr)) {
						node->locked = 0;
						unlock_job();
					}
				}
			}
			else if (!strncmp(ptr, "category ", 9)) {
				ptr += 9;
				cat = (category_t*) malloc(sizeof(category_t));
				cat->name = (char*) malloc(sizeof(char) * (strlen(ptr) + 1));
				strcpy(cat->name, ptr);
				cat->reqids = list_new();
				list_add(lock_reqs, cat, 0);
			}
		}
		else if (!strncmp(cmd, "nodes ", 6)) {
			ptr = cmd + 6;
			if (!strncmp(ptr, "add ", 4)) {
				ptr += 4;
				node = (node_t*) malloc(sizeof(node_t));
				node->name = (char*) malloc(sizeof(char) * (strlen(ptr) + 1));
				strcpy(node->name, ptr);
				node->locked = 0;
				node->disabled = 0;
				list_add(nodes, node, 0);
				unlock_job();
			}
			else if (!strncmp(ptr, "enable ", 7)) {
				ptr += 7;
				if (sscanf(ptr, "%s %d", buffer, &j) == 2) {
					for (i = list_size(nodes) - 1; i >= 0; i--) {
						node = (node_t*) list_get(nodes, i);
						if (!strcmp(node->name, buffer)) {
							if (node->disabled != j)
								break;
							node->disabled = 0;
							unlock_job();
						}
					}
				}
			}
			else if (!strncmp(ptr, "disable ", 8)) {
				ptr += 8;
				if (sscanf(ptr, "%s %d", buffer, &j) == 2) {
					for (i = list_size(nodes) - 1; i >= 0; i--) {
						node = (node_t*) list_get(nodes, i);
						if (!strcmp(node->name, buffer)) {
							if (node->disabled != 0)
								break;
							node->disabled = j;
						}
					}
				}
			}
		}
		if (!strncmp(cmd, "jobs ", 5)) {
			ptr = &cmd[5];
			if (sscanf(ptr, "%d", &job_id) == 1) {
				sscanf(ptr, "%s", buffer);
				ptr += strlen(buffer);
				if (ptr[0] == ' ' && !strncmp(&ptr[1], "finished ", 9)) {
					// Job finished (or aborted or killed or error occurred), lock not needed
					for (i = list_size(nodes) - 1; i >= 0; i--) {
						node = (node_t*) list_get(nodes, i);
						if (node == NULL) {
							fprintf(stderr, "BUG: locks: unable to get node %d\n", i);
							continue;
						}
						if (node->locked == job_id)
							node->locked = 0;
					}
					for (i = list_size(lock_reqs) - 1; i >= 0; i--) {
						cat = list_get(lock_reqs, i);
						for (j = list_size(cat->reqids) - 1; j >= 0; j--) {
							req = list_get(cat->reqids, j);
							if (req->job == job_id)
								free(list_remove(cat->reqids, j));
						}
					}
					unlock_job ();
				}
			}
		}
		else if (!strncmp(cmd, "help", 5)) {
			terminal_write_to_daemon(term, " * nodes add NODE_NAME");
			terminal_write_to_daemon(term, " * nodes enable NODE_NAME REASON_ID");
			terminal_write_to_daemon(term, " * nodes disable NODE_NAME REASON_ID");
			terminal_write_to_daemon(term, " * locks");
			terminal_write_to_daemon(term, " * locks unlock NODE_NAME");
			terminal_write_to_daemon(term, " * locks category CATEGORY_NAME");
		}
		free(cmd);
	}
}

void module_transaction(char* cmd, int output) {
	char* ptr = cmd;
	request_t* id;
	int i;
	char buffer[64];
	category_t* cat;

	if (!strcmp(cmd, "locks")) {
		print_status(output);
	}
	else if (!strncmp(ptr, "locks ", 6)) {
		ptr += 6;
		if (!strncmp(ptr, "request ", 8)) {
			ptr += 8;
			id = (request_t*) malloc(sizeof(request_t));
			id->id = next_request;
			if (sscanf(ptr, "%d", &id->job) == 1) {
				sscanf(ptr, "%s", buffer);
				ptr += strlen(buffer);
				if (ptr[0] == ' ') {
					ptr++;
					for (i = list_size(lock_reqs) - 1; i >= 0; i--) {
						cat = list_get(lock_reqs, i);
						if (!strcmp(ptr, cat->name)) {
							list_add(cat->reqids, id, id->id);
							sprintf(buffer, "locks request %d", next_request);
							write(output, buffer, strlen(buffer) + 1);
							next_request = (next_request % 65534) + 1;
							unlock_job();
							break;
						}
					}
				}
				else
					free(id);
			}
			else {
				free(id);
			}
		}
	}
}

int module_init(terminal_t* t) {
	pthread_t tid;

	nodes = list_new();
	lock_reqs = list_new();

	term = t;

	if (pthread_create(&tid, NULL, casper_locks, NULL) != 0) {
		fprintf(stderr, "ERROR: locks: unable to create thread\n");
		return errno;
	}
	fprintf(stderr, "INFO: locks: created management thread\n");

	return 0;
}
